package com.davfx.httpnio;


import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;

final class BasicConnection implements Connection {

	private static final int BUFFER_SIZE = 4 * 1024;
	
	private final Worker worker;
	private SocketChannel channel = null;
	private SelectionKey selectionKey = null;
	private ReadHandler readHandler = null;
	private IOException failed = null;
	private LinkedList<BasicConnection.Pair> toWriteQueue = new LinkedList<BasicConnection.Pair>();
	private boolean closed = false;
	private String host = null;
	private int port = -1;
	
	private static final class Pair {
		public final ByteBuffer buffer;
		public final WriteHandler handler;
		public Pair(ByteBuffer buffer, WriteHandler handler) {
			this.buffer = buffer;
			this.handler = handler;
		}
	}
	
	public BasicConnection(Worker worker) {
		this.worker = worker;
	}
	
	@Override
	public String getHost() {
		return host;
	}
	@Override
	public int getPort() {
		return port;
	}
		
	public void connected(Selector selector, final SocketChannel channel) throws IOException {
		host = channel.socket().getInetAddress().getHostAddress();
		port = channel.socket().getPort();
		this.channel = channel;
		selectionKey = channel.register(selector, 0);
		selectionKey.attach(new SelectionKeyVisitor() {
			@Override
			public void visit(final SelectionKey key) {
				if (key.isReadable()) {
					ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER_SIZE);
					try {
						int numRead = channel.read(readBuffer);
						if (numRead == -1) {
							closed = true;
							readHandler.closed();
							try {
								channel.close();
							} catch (IOException ee) {
							}
						} else {
							readBuffer.flip();
							readHandler.handle(readBuffer);
						}
					} catch (IOException e) {
						closed = true;
						readHandler.failed(e);
						try {
							channel.close();
						} catch (IOException ee) {
						}
					}
				} else if (key.isWritable()) {
					while (!toWriteQueue.isEmpty()) {
						BasicConnection.Pair p = toWriteQueue.getFirst();
						ByteBuffer b = p.buffer;
						try {
							channel.write(b);
						} catch (IOException e) {
							closed = true;
							for (BasicConnection.Pair pe : toWriteQueue) {
								pe.handler.failed(e);
							}
							toWriteQueue.clear();
							try {
								channel.close();
							} catch (IOException ee) {
							}
							break;
						}
						if (b.hasRemaining()) {
							return;
						}
						toWriteQueue.removeFirst();
					}
					if (!closed) {
						selectionKey.interestOps(selectionKey.interestOps() & ~SelectionKey.OP_WRITE);
					}
				}
			}
		});
		
		if (!toWriteQueue.isEmpty()) {
			selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_WRITE);
		}
		if (readHandler != null) {
			selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_READ);
		}
	}
	
	public void read(final ReadHandler handler) {
		worker.add(new SelectorVisitor() {
			@Override
			public void visit(final Selector selector) {
				if (closed) {
					handler.failed(new IOException("Closed connection"));
					return;
				}
				if (failed != null) {
					handler.failed(failed);
					return;
				}
				
				readHandler = handler;
				if (selectionKey != null) {
					selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_READ);
				}
			}
		});
	}
	
	public void write(final ByteBuffer buffer, final WriteHandler handler) {
		worker.add(new SelectorVisitor() {
			@Override
			public void visit(final Selector selector) {
				if (closed) {
					handler.failed(new IOException("Closed connection"));
					return;
				}
				if (failed != null) {
					handler.failed(failed);
					return;
				}
				
				toWriteQueue.addLast(new Pair(buffer, handler));
				if (selectionKey != null) {
					selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_WRITE);
				}
			}
		});
	}
	
	public void close() {
		worker.add(new SelectorVisitor() {
			@Override
			public void visit(final Selector selector) {
				if (closed) {
					return;
				}
				if (channel != null) {
					try {
						channel.close();
					} catch (IOException ee) {
					}
				}
				closed = true;
			}
		});
	}
	
	// called in worker thread
	void failed(IOException e) {
		failed = e;
	}

	// called in worker thread
	boolean isClosed() {
		return closed;
	}
}